package listeners

import (
	"blockchains-aio/conf"
	"blockchains-aio/fabric/ccc"
	"blockchains-aio/util"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"net/url"
	"strings"
)

func RegisterChainCodeInvocation(router *gin.Engine) {
	rootGroup := getRootGroup(router)
	cci := rootGroup.Group("/cci")

	cci.GET("/invocation/:orgId", queryChainCode)
	cci.POST("/invocation/:orgId", invokeChainCode)

	cci.GET("/origin/users/:id", getOriginalUsers)
	cci.GET("/users/:id", getUsers)
	cci.GET("/user/:id", getUserInHead)
}

type ChainCodeInvocationSpec struct {
	ChaincodeName string   `json:"chaincodeName"`
	ChannelId     string   `json:"channelId"`
	Function      string   `json:"function"`
	Args          []string `json:"args"`
}

//	type UTXO struct {
//		Key    string `json:"utxo_key"`
//		Owner  string `json:"owner"`
//		Amount int    `json:"amount"`
//	}
func getOriginalUsers(ctx *gin.Context) {
	orgId := ctx.Param("id")
	originaOrgcpplUsers := []string{
		"Admin@orgcpp.zhaowu.com",
		"recipient@orgcpp.zhaowu.com",
		"user2@orgcpp.zhaowu.com",
		//"User@orgcpp.zhaowu.com",
	}

	originaOrggolUsers := []string{
		"Admin@orggo.zhaowu.com",
		"minter@orggo.zhaowu.com",
		"user2@orggo.zhaowu.com",
		//"User@orggo.zhaowu.com",
	}

	if orgId == "orgcpp.zhaowu.com" {
		ctx.JSON(http.StatusOK, originaOrgcpplUsers)
	} else {
		ctx.JSON(http.StatusOK, originaOrggolUsers)
	}

}

func getUsers(ctx *gin.Context) {
	orgId := ctx.Param("id")

	//
	orgPath := conf.GetPeerOrganization(orgId)
	if !util.IsPathExists(orgPath) {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("could not find organization: %s", orgId))
		return
	}

	usersPath := fmt.Sprintf("%s%s", orgPath, "/users")
	users, err := util.GetAllFolders(usersPath)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, err)
		return
	}

	ctx.JSON(http.StatusOK, users)
}

type UserInHead struct {
	OrgName      string `json:"orgName"`
	MSPID        string `json:"MSPID"`
	CertPath     string `json:"certPath"`
	KeyPath      string `json:"keyPath"`
	TLSCertPath  string `json:"tLSCertPath"`
	PeerEndpoint string `json:"peerEndpoint"`
	GatewayPeer  string `json:"gatewayPeer"`
}

func getUserInHead(ctx *gin.Context) {
	var userInHead UserInHead
	userId := ctx.Param("id")
	idAtOrg := strings.Split(userId, "@")
	if len(idAtOrg) != 2 {
		ctx.JSON(http.StatusBadRequest, errors.New("userId form is userId@userOrg"))
		return
	}
	orgName := idAtOrg[1]
	orgPath := conf.GetPeerOrganization(orgName)
	userPath := fmt.Sprintf("%s/%s/%s", orgPath, "users", userId)
	if !util.IsPathExists(userPath) {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("could not find user path: %s", userPath))
		return
	}

	userInHead.OrgName = orgName
	if orgName == "orgcpp.zhaowu.com" {
		userInHead.MSPID = "OrgCppMSP"
		userInHead.PeerEndpoint = "dns:peer1.orgcpp.zhaowu.com:9051"
		userInHead.GatewayPeer = "peer1.orgcpp.zhaowu.com"
		userInHead.TLSCertPath = orgPath + "/peers/peer1.orgcpp.zhaowu.com/tls/ca.crt"
	} else { // assuming "orggo.zhaowu.com"
		userInHead.MSPID = "OrgGoMSP"
		userInHead.PeerEndpoint = "dns:///peer1.orggo.zhaowu.com:7051"
		userInHead.GatewayPeer = "peer1.orggo.zhaowu.com"
		userInHead.TLSCertPath = orgPath + "/peers/peer1.orggo.zhaowu.com/tls/ca.crt"
	}

	certPath, err := getFileFollowing(userPath + "/msp/signcerts")
	if err != nil {
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	userInHead.CertPath = certPath
	userInHead.KeyPath = userPath + "/msp/keystore"

	ctx.JSON(http.StatusOK, userInHead)
}

func getFileFollowing(path string) (string, error) {
	files, err := util.GetAllFiles(path, false)
	if err != nil {
		return "", err
	}

	return path + "/" + files[0], nil
}

func queryChainCode(ctx *gin.Context) {
	orgId := ctx.Param("orgId")
	log.Print("orgId:", orgId)
	orgPath := conf.GetPeerOrganization(orgId)

	//fCryptoConfigPath := conf.GetStackTemplateResDir("bft3", "crypto-config")
	//orgPath := fmt.Sprintf("%s/%s/%s", fCryptoConfigPath, "peerOrganizations", "orggo.zhaowu.com")

	config, ccis, err := validateAndCollect(ctx)
	log.Printf("config: %#v\n", config)
	log.Printf("ccis: %#v\n", ccis)
	if err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Cci, 1, "queryChainCode", fmt.Sprintf("%v", err)).Json())
		return
	}

	fabNetworkClient, err := ccc.New(orgPath, config["OrgName"], config["MSPID"], config["CertPath"], config["KeyPath"], config["TLSCertPath"], config["PeerEndpoint"], config["GatewayPeer"])
	if err != nil {
		//ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Cci, 2, "queryChainCode", fmt.Sprintf("%v", err)).Json())
		return
	}
	result, err := fabNetworkClient.Query(ccis.ChaincodeName, ccis.ChannelId, ccis.Function, ccis.Args)
	if err != nil {
		//ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Cci, 3, "queryChainCode", fmt.Sprintf(`
config=%#v,
ChaincodeName=%v
ChannelId=%v,
Function=%v,
Args=%v,
VerboseError=%v`, config, ccis.ChaincodeName, ccis.ChannelId, ccis.Function, ccis.Args, err)).Json())
		return
	}

	ctx.JSON(http.StatusOK, string(result))
	//fabNetworkClient.Close()

	return
}

func invokeChainCode(ctx *gin.Context) {
	orgId := ctx.Param("orgId")
	log.Print("orgId:", orgId)
	orgPath := conf.GetPeerOrganization(orgId)

	//fCryptoConfigPath := conf.GetStackTemplateResDir("bft3", "crypto-config")
	//orgPath := fmt.Sprintf("%s/%s/%s", fCryptoConfigPath, "peerOrganizations", "orggo.zhaowu.com")

	//config, ccis, err := validateAndCollect(ctx)
	config, ccis, err := validateAndCollectBody(ctx)
	log.Printf("config: %v", config)
	if err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Cci, 1, "invokeChainCode", fmt.Sprintf("%v", err)).Json())
		return
	}

	fabNetworkClient, err := ccc.New(orgPath, config["OrgName"], config["MSPID"], config["CertPath"], config["KeyPath"], config["TLSCertPath"], config["PeerEndpoint"], config["GatewayPeer"])
	if err != nil {
		//ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Cci, 2, "invokeChainCode", fmt.Sprintf("%v", err)).Json())
		return
	}
	result, err := fabNetworkClient.Invoke(ccis.ChaincodeName, ccis.ChannelId, ccis.Function, ccis.Args)
	if err != nil {
		//ctx.JSON(http.StatusInternalServerError, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Cci, 3, "invokeChainCode", fmt.Sprintf("%v", err)).Json())
		return
	}
	ctx.JSON(http.StatusOK, result)
	//fabNetworkClient.Close()

	return
}

func validateAndCollect(ctx *gin.Context) (conf map[string]string, ccis *ChainCodeInvocationSpec, err error) {
	ccis = new(ChainCodeInvocationSpec)
	conf = make(map[string]string, 7)
	val := ""
	// OrgName, MSPID, CertPath, KeyPath, TLSCertPath, PeerEndpoint, GatewayPeer
	if val = ctx.GetHeader("OrgName"); val == "" {
		err = errors.New("OrgName is unknown in the head")
		return
	}
	conf["OrgName"] = val

	if val = ctx.GetHeader("MSPID"); val == "" {
		err = errors.New("MSPID is unknown in the head")
		return
	}
	conf["MSPID"] = val

	if val = ctx.GetHeader("CertPath"); val == "" {
		err = errors.New("CertPath is unknown in the head")
		return
	}
	conf["CertPath"] = val

	if val = ctx.GetHeader("KeyPath"); val == "" {
		err = errors.New("KeyPath is unknown in the head")
		return
	}
	conf["KeyPath"] = val

	if val = ctx.GetHeader("TLSCertPath"); val == "" {
		err = errors.New("TLSCertPath is unknown in the head")
		return
	}
	conf["TLSCertPath"] = val

	if val = ctx.GetHeader("PeerEndpoint"); val == "" {
		err = errors.New("PeerEndpoint is unknown in the head")
		return
	}
	conf["PeerEndpoint"] = val

	if val = ctx.GetHeader("GatewayPeer"); val == "" {
		err = errors.New("GatewayPeer is unknown in the head")
		return
	}
	conf["GatewayPeer"] = val

	ccisStr := ctx.Query("ccis")
	if ccisStr == "" {
		ctx.JSON(http.StatusBadRequest, "ccis is neccessary in query string")
		return
	}
	ccisJson, err := url.QueryUnescape(ccisStr)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, errors.Join(errors.New("query unescape error:"), err))
		return
	}

	err = json.Unmarshal([]byte(ccisJson), ccis)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, errors.Join(errors.New("ccis unmarshal error:"), err))
		return
	}

	return
}

func validateAndCollectBody(ctx *gin.Context) (conf map[string]string, ccis *ChainCodeInvocationSpec, err error) {
	ccis = new(ChainCodeInvocationSpec)
	conf = make(map[string]string, 7)
	val := ""
	// OrgName, MSPID, CertPath, KeyPath, TLSCertPath, PeerEndpoint, GatewayPeer
	if val = ctx.GetHeader("OrgName"); val == "" {
		err = errors.New("OrgName is unknown in the head")
		return
	}
	conf["OrgName"] = val

	if val = ctx.GetHeader("MSPID"); val == "" {
		err = errors.New("MSPID is unknown in the head")
		return
	}
	conf["MSPID"] = val

	if val = ctx.GetHeader("CertPath"); val == "" {
		err = errors.New("CertPath is unknown in the head")
		return
	}
	conf["CertPath"] = val

	if val = ctx.GetHeader("KeyPath"); val == "" {
		err = errors.New("KeyPath is unknown in the head")
		return
	}
	conf["KeyPath"] = val

	if val = ctx.GetHeader("TLSCertPath"); val == "" {
		err = errors.New("TLSCertPath is unknown in the head")
		return
	}
	conf["TLSCertPath"] = val

	if val = ctx.GetHeader("PeerEndpoint"); val == "" {
		err = errors.New("PeerEndpoint is unknown in the head")
		return
	}
	conf["PeerEndpoint"] = val

	if val = ctx.GetHeader("GatewayPeer"); val == "" {
		err = errors.New("GatewayPeer is unknown in the head")
		return
	}
	conf["GatewayPeer"] = val

	//ccisStr := ctx.Query("ccis")
	//if ccisStr == "" {
	//	ctx.JSON(http.StatusBadRequest, "ccis is neccessary in query string")
	//	return
	//}
	//ccisJson, err := url.QueryUnescape(ccisStr)
	//if err != nil {
	//	ctx.JSON(http.StatusBadRequest, errors.Join(errors.New("query unescape error:"), err))
	//	return
	//}
	//
	//err = json.Unmarshal([]byte(ccisJson), ccis)
	//if err != nil {
	//	ctx.JSON(http.StatusBadRequest, errors.Join(errors.New("ccis unmarshal error:"), err))
	//	return
	//}
	if err = ctx.ShouldBind(ccis); err != nil {
		log.Println("binding body error", err)
	}

	return
}
